// EFServer
package tcpsocket

import (
	"bufio"
	"fmt"
	logging "log"
	"net"
	"net/http"
	_ "net/http/pprof"
	"runtime"
	"strconv"
	"sync"
	"time"
)

type EF_Server struct {
	readQuene, writeQuene      chan *BufferEvent
	host, port                 string
	connection_maps            map[string]*net.TCPConn
	connection_write_chan_maps map[string]chan *BufferEvent
	handler                    EventHandler
	debug                      bool
	pool                       *sync.Mutex
}

func NewEFServer(host string, port string) (s TcpServer) {
	cpu_count := runtime.NumCPU()
	s = &EF_Server{
		readQuene:                  make(chan *BufferEvent, cpu_count),
		writeQuene:                 make(chan *BufferEvent, cpu_count),
		host:                       host,
		port:                       port,
		connection_maps:            make(map[string]*net.TCPConn),
		connection_write_chan_maps: make(map[string]chan *BufferEvent),
		debug: true,
		pool:  new(sync.Mutex),
	}
	return
}

func (s *EF_Server) SetDebug(status bool) {
	s.debug = status
	if s.debug {
		_port, err := strconv.ParseInt(s.port, 10, 0)
		if err != nil {
			logging.Println(err)
			_port = 9998
		}
		_port += 1
		addr := fmt.Sprintf("%s:%d", s.host, _port)
		logging.Println("your profile url:http://", addr+"/debug/pprof/")
		go http.ListenAndServe(addr, nil)
	}
}

func (s *EF_Server) Start(handle EventHandler) (err error) {
	logging.Printf("Initiating server... (Ctrl-C to stop)")

	s.handler = handle

	remote := s.host + ":" + s.port
	addr, _err := net.ResolveTCPAddr("tcp", remote)
	if _err != nil {
		logging.Printf("Error creating listener: %s", _err)
		return
	}
	listener, err := net.ListenTCP("tcp", addr)
	if err != nil {
		logging.Printf("Error creating listener: %s", err)
		return
	}

	s.handler.RegisterServer(s)
	s.process(listener)
	return
}

func (s *EF_Server) Write(connkey string, content string) {
	buffer := BufferEvent{
		addr:   connkey,
		buffer: content,
	}
	s.writeQuene <- &buffer
}

func (s *EF_Server) Close(connkey string) {
	conn, status := s.connection_maps[connkey]
	if !status {
		return
	}
	go s.closeConn(conn)
}

func (s *EF_Server) process(_listener *net.TCPListener) {
	defer func() {
		_listener.Close()
		s.dispose()
	}()

	go s.handleRead()
	go s.handleWrite()

	for {
		conn, error := _listener.AcceptTCP()
		if error != nil {
			logging.Printf("Error: Accepting data: %s", error)
			continue
		}
		connkey := fmt.Sprintf("%s", conn.RemoteAddr())
		err := conn.SetKeepAlive(true)
		if err != nil {
			logging.Printf("Error:%s on SetKeepAlive addr: %s", err, connkey)
		}
		logging.Printf("Accept a new connection: %s", connkey)

		s.pool.Lock()
		s.connection_maps[connkey] = conn
		go s.handleConn(conn)
		s.pool.Unlock()
	}
}

func (s *EF_Server) dispose() {
	close(s.writeQuene)
	close(s.readQuene)
}

func (s *EF_Server) handleConn(conn *net.TCPConn) {
	connkey := fmt.Sprintf("%s", conn.RemoteAddr())
	defer s.Close(connkey)
	s.handler.HandleConnect(connkey)

	data := make([]byte, 1024)
	for {
		err := conn.SetReadDeadline(time.Now().Add(10 * time.Minute))
		if err != nil {
			s.handler.HandleError(connkey, err)
			break
		}

		n, _err := conn.Read(data)
		if _err != nil {
			logging.Printf("Error: Reading data : %s", _err)
			s.handler.HandleError(connkey, _err)
			break
		}
		buffer := BufferEvent{
			addr:   connkey,
			buffer: string(data[0:n]),
		}
		s.readQuene <- &buffer
	}
}

func (s *EF_Server) handleRead() {
	for {
		buffer, status := <-s.readQuene
		if !status {
			break
		}
		_, status = s.connection_maps[buffer.addr]
		if !status {
			continue
		}
		logging.Printf("get request of %s: %s", buffer.addr, buffer.buffer)
		s.handler.HandleReade(buffer.addr, buffer.buffer)
	}
}

func (s *EF_Server) handleWrite() {
	for {
		buffer, status := <-s.writeQuene
		if !status {
			break
		}
		conn, st := s.connection_maps[buffer.addr]
		if !st {
			continue
		}
		err := conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
		if err != nil {
			logging.Printf("client:%s, set timeout error:%s", buffer.addr, err)
			s.handler.HandleError(buffer.addr, err)
			go s.Close(buffer.addr)
			continue
		}
		go func() {
			writer := bufio.NewWriter(conn)
			bufferString := s.handler.HandleWrite(buffer.addr, buffer.buffer)
			_, err = writer.WriteString(bufferString)
			if err != nil {
				logging.Printf("client:%s, write error:%s", buffer.addr, err)
				s.handler.HandleError(buffer.addr, err)
				go s.Close(buffer.addr)
			}
			err = writer.Flush()
			if err != nil {
				logging.Printf("client:%s, write error:%s", buffer.addr, err)
				s.handler.HandleError(buffer.addr, err)
				go s.Close(buffer.addr)
			}
		}()
	}
}

func (s *EF_Server) closeConn(conn *net.TCPConn) {
	s.pool.Lock()
	defer s.pool.Unlock()

	if s.connection_maps == nil || len(s.connection_maps) == 0 {
		return
	}
	connkey := fmt.Sprintf("%s", conn.RemoteAddr())
	s.handler.HandleDisConnect(connkey)
	delete(s.connection_write_chan_maps, connkey)
	conn.Close()

	if s.debug {
		logging.Printf("client %s disconnect", connkey)
	}
}
